﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.IO;
using Bamboo.Utils;
using System.Xml;
using System.ComponentModel;
using System.Security.Policy;

namespace Bamboo.ExeShell
{
    public class Svn
    {
        public string UserName { get; set; }
        public string Password { get; set; }

        public Action<string> OutputHandler;
        public Action<string> ErrorHandler;

        /// <summary>
        /// 获取本地路径或者URL的最新版本号
        /// </summary>
        /// <param name="uri">指定目录或URL</param>
        /// <returns></returns>
        public int GetLatestRevision(string uri)
        {
            var proc = GetProc();
            proc.Arguments = "info " + uri + " --show-item last-changed-revision" + GetParam();
            var text = proc.OutputText.ToString().Trim();
            int revision = int.Parse(text);
            Log("获取SVN指定目录版本号成功，URL：" + uri + "，版本号：" + revision);
            return revision;
        }
        /// <summary>
        /// 获取多个本地目录或URL的最大版本号
        /// </summary>
        /// <param name="uris"></param>
        /// <returns></returns>
        public int GetLatestRevision(params string[] uris)
        {
            int maxRevision = 0;
            foreach (var uri in uris)
            {
                int rev = GetLatestRevision(uri);
                if (rev > maxRevision) maxRevision = rev;
            }
            return maxRevision;
        }
        /// <summary>
        /// 获取SVN仓库的最新版本号
        /// </summary>
        /// <param name="uri">指定目录或URL</param>
        /// <returns></returns>
        public int GetRepoRevision(string uri)
        {
            var proc = GetProc();
            proc.Arguments = "info " + uri + " --show-item revision" + GetParam();
            var text = proc.OutputText.ToString().Trim();
            int revision = int.Parse(text);
            Log("获取SVN仓库版本号成功，URL：" + uri + "，版本号：" + revision);
            return revision;
        }

        /// <summary>
        /// revert->checkout
        /// 如果失败则：删除目录->checkout
        /// </summary>
        /// <param name="url"></param>
        /// <param name="path"></param>
        /// <param name="revision">指定版本，-1代表最新版本</param>
        public void AutoUpdate(string url, string path, int revision)
        {
            Log(">>>>>>>>更新SVN目录到本地，URL：" + url + ", 目录：" + path + "，版本：" + revision);
            bool succ = true;
            succ = Directory.Exists(path);
            if (succ)
            {
                Log(">>>>>>>>正在执行cleanup");
                var proc = RunSvn("cleanup --remove-unversioned \"" + path + "\" " + GetParam(), true);
                succ = proc.ExitCode == 0;
            }
            if (succ)
            {
                Log(">>>>>>>>正在执行revert");
                var proc = RunSvn("revert -R \"" + path + "\" " + GetParam(), true);
                succ = proc.ExitCode == 0;
            }

            if (!succ)
            {
                Log(">>>>>>>>清理失败，正在删除目录");
                if (Directory.Exists(path)) Directory.Delete(path, true);
            }

            Log(">>>>>>>>正在执行checkout");
            string checkoutRevision = revision > 0 ? "@" + revision : "";
            RunSvn("checkout \"" + url + checkoutRevision + "\" \"" + path + "\" " + GetParam(), false);
        }
        /// <summary>
        /// 获取指定时间段内的SVN日志
        /// </summary>
        /// <param name="url">SVN地址</param>
        /// <param name="startTime">开始时间</param>
        /// <param name="endTime">结束时间</param>
        /// <param name="verbose">是否获取变更文件列表，对应命令行-v参数</param>
        /// <returns></returns>
        public List<SvnLogItem> GetLog(string url, DateTime startTime, DateTime endTime, bool verbose)
        {
            var proc = GetProc();
            string t1 = startTime.ToString("yyyy-MM-ddTHH:mm:ss");
            string t2 = endTime.ToString("yyyy-MM-ddTHH:mm:ss");
            string verboseText = verbose ? " -v" : "";
            proc.Arguments = "log " + url + " -r {" + t1 + "}:{" + t2 + "}" + verboseText + " --xml";
            proc.EncodingName = "utf-8";
            proc.DebugMode = false;
            proc.OutputHandler = null;
            proc.Run();
            var list = ParseSvnLogXml(proc.OutputText.ToString(), verbose);
            return list;
        }

        /// <summary>
        /// 获取指定时间段内的SVN日志
        /// </summary>
        /// <param name="url">SVN地址</param>
        /// <param name="startTime">开始时间</param>
        /// <param name="endTime">结束时间</param>
        /// <param name="verbose">是否获取变更文件列表，对应命令行-v参数</param>
        /// <returns></returns>
        public List<SvnLogItem> GetLog(string url, string r1, string r2, bool verbose)
        {
            var proc = GetProc();
            string verboseText = verbose ? " -v" : "";
            proc.Arguments = "log " + url + " -r " + r1 + ":" + r2 + verboseText + " --xml";
            proc.EncodingName = "utf-8";
            proc.DebugMode = false;
            proc.OutputHandler = null;
            proc.Run();
            var list = ParseSvnLogXml(proc.OutputText.ToString(), verbose);
            return list;
        }
        /// <summary>
        /// 解析svn log命令返回的xml文本
        /// </summary>
        /// <param name="input"></param>
        /// <param name="verbose"></param>
        /// <returns></returns>
        public static List<SvnLogItem> ParseSvnLogXml(string input, bool verbose)
        {
            //解析结果
            var xml = new XmlDocument();
            xml.LoadXml(input);
            var nodelist = xml.DocumentElement.SelectNodes("/log/logentry");
            var list = new List<SvnLogItem>();
            foreach (XmlNode node in nodelist)
            {
                var item = new SvnLogItem();
                item.Revision = node.GetAttrValue("revision", -1);
                item.Author = node.GetSubNodeValue("author", "");
                string dateText = node.GetSubNodeValue("date", "");
                DateTime tmpdate;
                TryTextToDate(dateText, out tmpdate);
                item.ModifyTime = tmpdate;
                item.Message = node.GetSubNodeValue("msg", "");
                //解析文件路径
                if (verbose)
                {
                    item.PathList = new List<SvnLogFileItem>();
                    var pathsNode = node.GetSubNode("paths");
                    if (pathsNode == null) continue;
                    foreach (XmlNode pathNode in pathsNode.ChildNodes)
                    {
                        if (pathNode.Name != "path") continue;
                        var pathItem = new SvnLogFileItem();
                        pathItem.Revision = item.Revision;
                        pathItem.TextMods = pathNode.GetAttrValue("text-mods", true);
                        pathItem.Kind = pathNode.GetAttrValue("kind", "");
                        pathItem.Action = pathNode.GetAttrValue("action", "");
                        pathItem.PropMods = pathNode.GetAttrValue("prop-mods", false);
                        pathItem.Path = pathNode.InnerText;
                        item.PathList.Add(pathItem);
                    }
                }
                list.Add(item);
            }
            return list;
        }
        /// <summary>
        /// 尝试将SVN日志中的日期字符串转成DateTime类型
        /// input的例子：2024-5-15T09:10:30.320Z
        /// </summary>
        /// <param name="input"></param>
        /// <param name="output"></param>
        /// <returns></returns>
        public static bool TryTextToDate(string input, out DateTime output)
        {
            try
            {
                var parts = input.Split('-', 'T', ':', '.', 'Z');
                if (parts.Length < 7) { output = DateTime.MinValue; return false; }
                int year, month, day, hour, minutes, seconds, us;
                if (!int.TryParse(parts[0], out year)) { output = DateTime.MinValue; return false; }
                if (!int.TryParse(parts[1], out month)) { output = DateTime.MinValue; return false; }
                if (!int.TryParse(parts[2], out day)) { output = DateTime.MinValue; return false; }
                if (!int.TryParse(parts[3], out hour)) { output = DateTime.MinValue; return false; }
                if (!int.TryParse(parts[4], out minutes)) { output = DateTime.MinValue; return false; }
                if (!int.TryParse(parts[5], out seconds)) { output = DateTime.MinValue; return false; }
                if (!int.TryParse(parts[6], out us)) { output = DateTime.MinValue; return false; }
                output = new DateTime(year, month, day, hour, minutes, seconds, us / 1000);
                return true;
            }
            catch (Exception)
            {
                output = DateTime.MinValue;
                return false;
            }
        }

        private ProcessExecute RunSvn(string arguments, bool ignoreError)
        {
            var proc = GetProc();
            proc.Arguments = arguments;
            proc.IgnoreError = ignoreError;
            proc.Run();
            return proc;
        }

        private ProcessExecute GetProc()
        {
            var proc = new ProcessExecute();
            proc.FileName = "svn.exe";
            proc.OutputHandler = Log;
            proc.ErrorHandler = LogError;
            proc.IgnoreError = false;
            return proc;
        }

        private void Log(string s)
        {
            if (OutputHandler != null) OutputHandler(s);
        }

        private void LogError(string s)
        {
            if (OutputHandler != null) OutputHandler(s);
        }

        private string GetParam()
        {
            return GetCommonParam(UserName, Password);
        }
        /// <summary>
        /// 返回通用SVN参数：
        /// --non-interactive 
        /// --username foo 
        /// --password bar
        /// --trust-server-cert-failures=unknown-ca,cn-mismatch,expired,not-yet-valid,other
        /// </summary>
        /// <param name="user"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public static string GetCommonParam(string user, string password)
        {
            if (string.IsNullOrWhiteSpace(user))
            {
                return " --non-interactive --trust-server-cert-failures=unknown-ca,cn-mismatch,expired,not-yet-valid,other";
            }
            else
            {
                return " --non-interactive --username " + user + " --password " + password +
                    " --trust-server-cert-failures=unknown-ca,cn-mismatch,expired,not-yet-valid,other";
            }
            
        }
    }

    public class SvnInfoRet
    {
        /// <summary>
        /// 当前目录
        /// </summary>
        public string Path { get; set; }
        public string RepositoryUUID { get; set; }
        /// <summary>
        /// 当前目录所对应的URL
        /// </summary>
        public string URL { get; set; }
        /// <summary>
        /// 当前目录的版本号
        /// </summary>
        public int Revision { get; set; }
        /// <summary>
        /// 上次修改的版本
        /// </summary>
        public int LastChangedRevision { get; set; }
    }

    public class SvnLogItem
    {
        [DisplayName("版本号"), ColumnWidth(10)]
        public long Revision { get; set; }
        [DisplayName("作者"), ColumnWidth(10)]
        public string Author { get; set; }
        [DisplayName("时间"), ColumnWidth(20)]
        public DateTime ModifyTime { get; set; }
        [DisplayName("修改说明"), ColumnWidth(40)]
        public string Message { get; set; }
        public List<SvnLogFileItem> PathList;
    }

    public class SvnLogFileItem
    {
        [DisplayName("版本号"), ColumnWidth(10)]
        public long Revision { get; set; }
        [DisplayName("文本模式"), ColumnWidth(10)]
        public bool TextMods { get; set; }
        [DisplayName("种类"), ColumnWidth(10)]
        public string Kind { get; set; }
        [DisplayName("动作"), ColumnWidth(10)]
        public string Action { get; set; }
        [DisplayName("prop-mods"), ColumnWidth(10)]
        public bool PropMods { get; set; }
        [DisplayName("路径"), ColumnWidth(40)]
        public string Path { get; set; }
    }
}
